////////////////////////////////////////////////
// DESCRIPTION:
//    Support for emulating HARDWARE FIFOs
//
// Legal Notices:
//   Copyright (c) 2008, Telliam Consulting, LLC.
//   All rights reserved.
//
//   Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice, this list
//     of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice, this
//     list of conditions and the following disclaimer in the documentation and/or other
//     materials provided with the distribution.
//   * Neither the name of Telliam Consulting nor the names of its contributors may be
//     used to endorse or promote products derived from this software without specific
//     prior written permission.
//
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
//   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
//   SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
//   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
//   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
//   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//   DAMAGE. 
//
using System;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace FusionWare.SPOT.EmulatorComponents
{
    /// <summary>class for implementing hardware FIFOs in emulation</summary>
    /// <typeparam name="T">type of data stored in the FIFO</typeparam>
    public class Fifo<T> where T : new()
    {
        // Fields
        private T[] Buffer;
        private volatile int ReadIndex;
        private volatile int WriteIndex;

        private object ReadSynch = new object();
        private object WriteSynch = new object();

        /// <summary>Creates a new FIFO instance of a fixed size</summary>
        /// <param name="Capacity"></param>
        public Fifo(int Capacity)
        {
            this.Buffer = new T[Capacity];
        }

        /// <summary>Retrieves the FIFO capacity</summary>
        /// <value>Maximum number of items the FIFO holds</value>
        public int Capacity
        {
            get { return this.Buffer.Length; }
        }

        /// <summary>Flushes the FIFO</summary>
        /// <remarks>
        /// Any data in the FIFO is removed and the total count of bytes is set to 0
        /// </remarks>
        public void Flush()
        {
            lock(ReadSynch)
            {
                lock(WriteSynch)
                {
                    this._Count = this.ReadIndex = this.WriteIndex = 0;
                }
            }
        }

        #region Properties
        /// <summary>Checks if the FIFO is empty</summary>
        /// <value>true if the FIFO is empty; false if not</value>
        public bool IsEmpty
        { 
            get { return this._Count == 0; }
        }
        /// <summary>Checks if the FIFO is FULL</summary>
        /// <value>true if the FIFO is FULL; false if not</value>
        public bool IsFull
        { 
            get { return this._Count == this.Buffer.Length; }
        }

        /// <summary>Gets the Count of items in the FIFO</summary>
        /// <value>Number of items in the FIFO</value>
        public int Count
        { 
            get { return _Count; }
        }
        volatile int _Count;

        /// <summary>Value to return from reads if the FIFO is empty</summary>
        /// <value>The value to return when reading from an empty FIFO</value>
        /// <remarks>
        /// <para>This value defaults to an instance of T initialized from the 
        /// default parameter less constructor. Ordinarily that is sufficient
        /// for most systems. However, this property allows setting the 
        /// empty value as desired.</para>
        /// 
        /// <para>The Empty value is only returend if <see cref="ThrowOnReadWhenEmpty"/>
        /// is false since if it is true an exception is thrown instead of returning the
        /// empty value</para>
        /// </remarks>
        public T EmptyValue
        {
            get { return _EmptyValue; }
            set { _EmptyValue = value; }
        }
        private T _EmptyValue = new T();

        /// <summary>Flag to determine how writes to a FULL FIFO is handled</summary>
        /// <value>true if writing to a FULL FIFO throws an exception; false if it isignored</value>
        /// <remarks>
        /// In many cases of emulating hardware it is desireable to simply ignore data
        /// written to an already FULL FIFO. In other cases it can be useful to throw
        /// an exception. This flag determines which behaviour is used. 
        /// </remarks>
	    public bool ThrowOnWriteWhenFull
	    {
            get { return _ThrowOnWriteWhenFull; }
            set { _ThrowOnWriteWhenFull = value; }
	    }
        private bool _ThrowOnWriteWhenFull;

        /// <summary>Flag to determine how reads from an empty FIFO is handled</summary>
        /// <value>true if reading from an empty FIFO throws an exception; false if it returns <see cref="EmptyValue"/></value>
        /// <remarks>
        /// In many cases of emulating hardware it is desireable to simply return a "zero"
        /// value when reading from an empty FIFO. In other cases it can be useful to throw
        /// an exception. This flag determines which behaviour is used. 
        /// </remarks>
        public bool ThrowOnReadWhenEmpty
	    {
		    get { return _ThrowOnReadWhenEmpty;}
		    set { _ThrowOnReadWhenEmpty = value;}
	    }
        private bool _ThrowOnReadWhenEmpty;
        #endregion
      
        #region Read
        /// <summary>Reads one item from the FIFO</summary>
        /// <returns>Item read from the FIFO</returns>
        /// <remarks>
        /// The behavior of this method when the FIFO is empty 
        /// depends on the value of <see cref="ThrowOnReadWhenEmpty"/>
        /// </remarks>
        /// <exception cref="InvalidOperationException">When reading from an empty FIFO and<see cref="ThrowOnReadWhenEmpty"/> is true </exception>
        public T Read()
        {
            if(IsEmpty)
            {
                if(this._ThrowOnReadWhenEmpty)
                    throw new InvalidOperationException("Attempt to read from an empty FIFO");
                else
                    return this._EmptyValue;
            }

            return InternalRead();
        }

        /// <summary>Reads an array of items from the FIFO</summary>
        /// <param name="ReadBuffer">buffer to read items into</param>
        /// <param name="Offset">offset in buffer to start reading items into</param>
        /// <param name="Count">count of items to read</param>
        /// <remarks>
        /// The behavior of this method when the FIFO is empty 
        /// depends on the value of <see cref="ThrowOnReadWhenEmpty"/>
        /// </remarks>
        /// <exception cref="InvalidOperationException">When reading from an empty FIFO and<see cref="ThrowOnReadWhenEmpty"/> is true </exception>
        public void Read(T[] ReadBuffer, int Offset, int Count)
        {
            for(int i=Offset; i< Offset + Count; ++i)
                ReadBuffer[i] = Read();
        }

        private T InternalRead()
        {
            lock(this.ReadSynch)
            {
                T retVal = this.Buffer[this.ReadIndex];
                this.Buffer[this.ReadIndex] = this.EmptyValue;
                
                this._Count -= 1;
                this.ReadIndex = (this.ReadIndex + 1) % this.Buffer.Length;
                
                return retVal;
            }
        }
        #endregion
        
        #region Write
        /// <summary>Writes a single item to the FIFO</summary>
        /// <param name="Value">value to write to the FIFO</param>
        /// <remarks>
        /// The behaviour of this method when the FIFO is full is
        /// defined by the <see cref="ThrowOnWriteWhenFull"/> property
        /// value. 
        /// </remarks>
        /// <exception cref="ThrowOnWriteWhenFull">Thrown when a write is made to a full FIFO and the <see cref="ThrowOnWriteWhenFull"/> property is true.</exception>
        public void Write(T Value)
        {
            if(IsFull)
            {
                if(this._ThrowOnWriteWhenFull)
                    throw new InvalidOperationException("Attempt to read from an empty FIFO");
                else
                    return;
            }

            InternalWrite(Value);
        }

        /// <summary>Writes an array of items to the FIFO</summary>
        /// <param name="WriteBuffer">Array of items to write</param>
        /// <param name="Offset">index in Buffer to start writing from</param>
        /// <param name="Count">Count of items to write</param>
        /// <remarks>
        /// The behaviour of this method when the FIFO is full is
        /// defined by the <see cref="ThrowOnWriteWhenFull"/> property
        /// value. 
        /// </remarks>
        /// <exception cref="ThrowOnWriteWhenFull">Thrown when a write is made to a full FIFO and the <see cref="ThrowOnWriteWhenFull"/> property is true.</exception>
        public void Write(T[] WriteBuffer, int Offset, int Count)
        {
            for(int i = Offset; i < Offset + Count; ++i)
                Write(WriteBuffer[i]);
        }

        private void InternalWrite(T Value)
        {
            lock(this.WriteSynch)
            {
                
                this.Buffer[this.WriteIndex] = Value;

                this._Count += 1;
                this.WriteIndex = (this.WriteIndex + 1) % this.Buffer.Length;
            }
        }
        #endregion
    }
}
